unit MainUnit1;

interface


uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, AppEvnts, ExtCtrls, ImgList, Menus, ShellApi;

const WH_MOUSE_LL = 14;
const
  WM_ICONTRAY = WM_USER + 1;   // User-defined message
  //NIF_INFO = $10;
  //NIIF_INFO = $00000001;

type
  TMyRect = record
    Left, Top, Width, Height, SkipClicks: integer;
    Action, Name, ActionAfterSkipping: string;
  end;
  TForm1 = class(TForm)
    ApplicationEvents1: TApplicationEvents;
    TimerTapZoneHide: TTimer;
    ImageList1: TImageList;
    PopupMenu1: TPopupMenu;
    PopupMenuAreYouSure: TPopupMenu;
    Exit1: TMenuItem;
    Areyousure1: TMenuItem;
    Label1: TLabel;
    N1: TMenuItem;
    Showtapzones1: TMenuItem;
    TimerTrayIconDblClick: TTimer;
    Getclassofactivewindow1: TMenuItem;

    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);

    procedure ApplicationEvents1Message(var Msg: tagMSG;
      var Handled: Boolean);
    procedure TimerTapZoneHideTimer(Sender: TObject);
    procedure Exit1Click(Sender: TObject);
    procedure Areyousure1Click(Sender: TObject);
    procedure Showtapzones1Click(Sender: TObject);
    procedure TimerTrayIconDblClickTimer(Sender: TObject);
    procedure Getclassofactivewindow1Click(Sender: TObject);
  private
    FHookStarted : Boolean;
  public
    function GetActiveWindowClass: string;
    procedure TrayIconChange(IconAction: Byte; SCSreenOnOff: boolean);
    procedure TrayIconEvent(var Msg: TMessage); message WM_ICONTRAY;
  end;
  TTimeoutOrVersion = record
    case Integer of
      0: (uTimeout: UINT);
      1: (uVersion: UINT);
  end;
  TNewNotifyIconData = record
    cbSize: DWORD;
    Wnd: HWND;
    uID: UINT;
    uFlags: UINT;
    uCallbackMessage: UINT;
    hIcon: HICON;
   //Version 5.0 is 128 chars, old ver is 64 chars
    szTip: array [0..127] of Char;
    dwState: DWORD; //Version 5.0
    dwStateMask: DWORD; //Version 5.0
    szInfo: array [0..255] of Char; //Version 5.0
    TimeoutOrVersion: TTimeoutOrVersion;
    szInfoTitle: array [0..63] of Char; //Version 5.0
    dwInfoFlags: DWORD;   //Version 5.0
  end;

var
  Form1: TForm1;

implementation

uses Types, IniFiles;

{$R *.dfm}

const rect1: TRect = (Left: 100; Top: 100; Right: 200; Bottom: 200);

var JHook: THandle;
    rects: array of TMyRect;
    SkipClicks, ActivatedTapZone: integer;
    AllowedWinClasses: TStringList;

procedure LoadRects;
var ini_file: TIniFile;
    i, OriginalScreenWidth, OriginalScreenHeight: integer;
begin
  ini_file := TIniFile.Create(ExtractFilePath(Application.ExeName) + 'scscreen.ini');

  AllowedWinClasses.Clear;

  AllowedWinClasses.CommaText := ini_file.ReadString('Main', 'AllowedWinClasses', '');

  OriginalScreenWidth := ini_file.ReadInteger('Main', 'OriginalScreenWidth', Screen.Width);
  OriginalScreenHeight := ini_file.ReadInteger('Main', 'OriginalScreenHeight', Screen.Height);

  //HookAutoStart=1
  //AllowedWindowClasses=
  SetLength(rects, ini_file.ReadInteger('Main', 'TapZoneCount', 0));
  for i := 0 to Length(rects) - 1 do
    begin
      rects[i].Left := Round(ini_file.ReadInteger('TapZone' + IntToStr(i + 1), 'Left', 0) * Screen.Width/OriginalScreenWidth);
      rects[i].Top := Round(ini_file.ReadInteger('TapZone' + IntToStr(i + 1), 'Top', 0) * Screen.Height/OriginalScreenHeight);
      rects[i].Width := Round(ini_file.ReadInteger('TapZone' + IntToStr(i + 1), 'Width', 0) * Screen.Width/OriginalScreenWidth);
      rects[i].Height := Round(ini_file.ReadInteger('TapZone' + IntToStr(i + 1), 'Height', 0) * Screen.Height/OriginalScreenHeight);

      rects[i].Action := ini_file.ReadString('TapZone' + IntToStr(i + 1), 'Action', '');
      rects[i].Name := ini_file.ReadString('TapZone' + IntToStr(i + 1), 'Name', 'No name');

      rects[i].SkipClicks := ini_file.ReadInteger('TapZone' + IntToStr(i + 1), 'SkipClicks', 0);
      rects[i].ActionAfterSkipping := ini_file.ReadString('TapZone' + IntToStr(i + 1), 'ActionAfterSkipping', '');
    end;

  ini_file.Free;


  SkipClicks := 0;
  ActivatedTapZone := -1;
  //ClickCaught := false;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  SetWindowLong(Application.Handle, GWL_EXSTYLE, WS_EX_TOOLWINDOW or WS_EX_TOPMOST);
  TrayIconChange(1, false);

  TimerTrayIconDblClick.Interval := GetDoubleClickTime;
  AllowedWinClasses := TStringList.Create;

  LoadRects;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  AllowedWinClasses.Free;

  if FHookStarted then
    UnhookWindowsHookEx(JHook);
  TrayIconChange(2, false);
end;


function MouseHookProc(Code, wParam: Integer; var MouseHookStrut: TMouseHookStruct): Integer; stdcall;
var //s: string;
    x, y, i: integer;
    block_event: boolean;
    TBRect: TRect; // taskbar rectangle
    //cur_rect: TRect;
  procedure TaskBarRect;
  var hTB: HWND; // taskbar handle
  begin
    hTB:= FindWindow('Shell_TrayWnd', '');
    if hTB = 0 then
      TBRect := Rect(0,0,0,0)
    else GetWindowRect(hTB, TBRect);
  end;

  procedure ActivateTapZone(CurTZ, ActType: integer);
  var cur_name, cur_action: string;
  begin
    case ActType of
      1:
        begin
          cur_name := rects[CurTZ].Name;
          cur_action := rects[CurTZ].Action;
        end;
      2:
        begin
          cur_name := rects[CurTZ].Name + '+';
          cur_action := rects[CurTZ].ActionAfterSkipping;
        end;
    else
        begin
          cur_name := rects[CurTZ].Name + '?';
          cur_action := '';
        end;
    end;

    if (ActType = 1) or (cur_action <> '') then
      begin
        Form1.Label1.Caption := cur_name;
        Form1.Refresh;
        if (cur_action <> '') then
          ShellExecute(Application.Handle, nil, PAnsiChar(cur_action), nil, nil, SW_SHOWNOACTIVATE);

        Form1.Left := rects[curTZ].Left;
        Form1.Top := rects[curTZ].Top;
        Form1.Width := rects[curTZ].Width;
        Form1.Height := rects[curTZ].Height;

        //Form1.Show;
        ShowWindow(Form1.Handle, SW_SHOWNOACTIVATE);
        Form1.Visible := True;
        Form1.TimerTapZoneHide.Enabled := true;
      end;
  end;

  function CheckWindowClass: boolean;
  var tmp_str: string;
  begin
    tmp_str := Form1.GetActiveWindowClass;
    result :=
      (AllowedWinClasses.IndexOf(tmp_str) >= 0) or
      (AllowedWinClasses.Text = '');
  end;

begin
   {this is the JournalRecordProc}
   //Result := CallNextHookEx(JHook, Code, wParam, Longint(@EventStrut));
   {the CallNextHookEX is not really needed for journal hook since it it not
  really in a hook chain, but it's standard for a Hook}
   //if Code < 0 then Exit;

   {you should cancel operation if you get HC_SYSMODALON}
   //if Code = HC_SYSMODALON then Exit;
   if Code = HC_ACTION then
   begin
     {
    The lParam parameter contains a pointer to a TEventMsg
    structure containing information on
    the message removed from the system message queue.
    }
     //s := '';
     block_event := false;

     case wParam of
       WM_LBUTTONUP:
         begin
           //s := 'Left Mouse UP';
           //if (ClickCaught) then
           //  begin
           //    block_event := true;
           //    ClickCaught := false;
           //  end;
           if (SkipClicks > 0) then
             begin
               Dec(SkipClicks);
               if (SkipClicks <= 0) and (ActivatedTapZone > 0) then
                 begin
                   ActivateTapZone(ActivatedTapZone, 2);
                   ActivatedTapZone := -1;
                 end;
             end;
         end;
       WM_LBUTTONDOWN:
         begin
            if (SkipClicks <= 0) then
              begin
               //s := 'Left Mouse Down';
                x := MouseHookStrut.pt.X;
                y := MouseHookStrut.pt.Y;

                TaskBarRect;
                if not ((TBRect.Left <= x) and (x <= TBRect.Right) and
                   (TBRect.Top <= y) and (y <= TBRect.Bottom)) then
                if (CheckWindowClass) then
                for i := 0 to Length(rects) - 1 do
                  begin
                    if (rects[i].Left <= x) and (x <= rects[i].Left + rects[i].Width) and
                       (rects[i].Top <= y) and (y <= rects[i].Top + rects[i].Height) then
                      begin
                        //s := s + ' hit';
                        block_event := true;

                        ActivateTapZone(i, 1);

                        SkipClicks := rects[i].SkipClicks + 1;
                        ActivatedTapZone := i;
                        //ClickCaught := true;
                      end;
                  end;
               end;
         end;
       //WM_RBUTTONDOWN: s := 'Right Mouse Down';
       //WM_RBUTTONUP: s := 'Right Mouse Up';
       //WM_MOUSEWHEEL: s := 'Mouse Wheel';
       WM_MOUSEMOVE:
         begin
           //s := 'Mouse Position';
           //if (ClickCaught) then
           //  block_event := true;
         end;
     end;

     //if (s <> '') then
     //  s := Format('%s, x=%d, y=%d', [s, MouseHookStrut.pt.X, MouseHookStrut.pt.Y]);

     if (block_event) then
       begin
         Result := -1;
       end
     else
     Result := CallNextHookEx(JHook, Code, wParam, Longint(@MouseHookStrut));

     //if s <> '' then
     //   Form1.ListBox1.ItemIndex :=  Form1.ListBox1.Items.Add(s + ' ' + IntToStr(Result));
   end
  else
    begin
      Result := CallNextHookEx(JHook, Code, wParam, Longint(@MouseHookStrut));
      //Form1.ListBox1.ItemIndex :=  Form1.ListBox1.Items.Add('++');
    end;
end;



 procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
begin
   {the journal hook is automaticly camceled if the Task manager
  (Ctrl-Alt-Del) or the Ctrl-Esc keys are pressed, you restart it
  when the WM_CANCELJOURNAL is sent to the parent window, Application}
   Handled := False;
   if (Msg.message = WM_CANCELJOURNAL) and FHookStarted then
     JHook := SetWindowsHookEx(WH_JOURNALRECORD, @MouseHookProc, 0, 0);
end;

procedure TForm1.TimerTapZoneHideTimer(Sender: TObject);
begin
  //Hide;
  Form1.Visible := False;
  TimerTapZoneHide.Enabled := false;
  Label1.Caption := '';
end;

procedure TForm1.TrayIconChange(IconAction: Byte; SCSreenOnOff: boolean);
Var Nim: TNewNotifyIconData;
begin
  Nim.cbSize := SizeOf(Nim);
  StrPCopy(Nim.szTip, Application.Title);
  Nim.uFlags := NIF_MESSAGE + NIF_ICON + NIF_TIP;

  Nim.Wnd := Handle;
  Nim.uID := 1;
  Nim.uCallbackMessage := WM_ICONTRAY;

  if (SCSreenOnOff) then
    ImageList1.GetIcon(1, Application.Icon)
  else ImageList1.GetIcon(0, Application.Icon);

  Nim.hIcon := Application.Icon.Handle;

  case IconAction OF
    1: Shell_NotifyIcon(NIM_ADD,@Nim);
    2: Shell_NotifyIcon(NIM_DELETE,@Nim);
    3: Shell_NotifyIcon(NIM_MODIFY,@Nim);
  end;
end;

procedure TForm1.TrayIconEvent(var Msg: TMessage);
var p: TPoint;
begin
  if Msg.lParam = WM_LBUTTONDOWN then
    TimerTrayIconDblClick.Enabled := true
  else if (Msg.lParam = WM_RBUTTONDOWN) or (Msg.LParam = WM_LBUTTONDBLCLK) then
    begin
      TimerTrayIconDblClick.Enabled := false;
      GetCursorPos(p);
      PopupMenu1.Popup(p.x, p.y);
    end
  else inherited;
end;

procedure TForm1.Exit1Click(Sender: TObject);
var p: TPoint;
begin
  GetCursorPos(p);
  PopupMenuAreYouSure.Popup(p.x, p.y);
end;

procedure TForm1.Areyousure1Click(Sender: TObject);
begin
  Close;
end;

procedure TForm1.Showtapzones1Click(Sender: TObject);
var i: integer;
  desktopCanvas : TCanvas;
begin
  LoadRects;
  desktopCanvas := TCanvas.Create;
  desktopCanvas.Handle := GetDC(0);
  try
    for i := 0 to Length(rects) - 1 do
      begin
        desktopCanvas.Rectangle(rects[i].Left, rects[i].Top, rects[i].Left + rects[i].Width, rects[i].Top + rects[i].Height);
        desktopCanvas.Font.Size := 12;
        desktopCanvas.Font.Style := [fsBold];
        desktopCanvas.TextRect(
          Rect(rects[i].Left + 1, rects[i].Top + 1, rects[i].Left + rects[i].Width - 1, rects[i].Top + rects[i].Height - 1),
          rects[i].Left + 1, Round(rects[i].Top + 1 -desktopCanvas.Font.Height*1.2), Format('%s / %d / %s', [rects[i].Action, rects[i].SkipClicks, rects[i].ActionAfterSkipping]));
        desktopCanvas.TextOut(rects[i].Left + 1, rects[i].Top + 1, Format('#%d %s', [i, rects[i].Name]));
      end;
  finally
   desktopCanvas.Free;
  end;

end;


function TForm1.GetActiveWindowClass: string;
var hwnd1: HWND;
    //Len: LongInt;
    //Title: string;
    aName: array [0..255] of Char;
begin
  result := '';
  hwnd1 := GetForegroundWindow;
  if hwnd1 <> 0 then
    begin
      //Len := 256; //GetWindowTextLength(hwnd1) + 1;
      //SetLength(Title, Len);
      //GetWindowText(hwnd1, PChar(Title), Len);

      GetClassName(hwnd1, aName, 256);
      //Label1.Caption := '"' + string(aName) + '"';

      result := string(aName);   //( = 'classFoxitReader');
    end;
end;

procedure TForm1.TimerTrayIconDblClickTimer(Sender: TObject);
begin
  TimerTrayIconDblClick.Enabled := false;
      if (FHookStarted) then
        begin
          //ShowMessage('Mouse is already being Journaled, can not restart');
          FHookStarted := False;
          UnhookWindowsHookEx(JHook);
          JHook := 0;
        end
      else
        begin
          JHook := SetWindowsHookEx(WH_MOUSE_LL, @MouseHookProc, hInstance, 0);
          if (JHook > 0) then
              FHookStarted := True
          else ShowMessage('No Hook availible');
        end;

      TrayIconChange(3, FHookStarted);
end;

procedure TForm1.Getclassofactivewindow1Click(Sender: TObject);
begin
  //ShowMessage();
  Sleep(1000);
  InputBox(
    'SCScreen',
    'Copy class of active window to ini-file Main/AllowedWinClasses',
    GetActiveWindowClass);
end;

end.
